package com.idanchuang.support.jboss.access.log;

import com.alibaba.fastjson.JSON;
import org.apache.skywalking.apm.toolkit.trace.TraceContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 访问日志
 */
public class WebAccessFilter implements Filter {

    private static final Logger logger = LoggerFactory.getLogger(WebAccessFilter.class);
    /**
     * 处理时间 线程绑定
     */
    private static final ThreadLocal<Long> DEAL_TIME_THREAD_LOCAL = new ThreadLocal<>();

    /**
     * 请求来源参数名
     */
    public static final String RPC_FROM_NAME = "rpc_from";
    public static final String RPC_FROM_API_NAME = "Rpc-From-Api";
    public static final String RPC_FROM_GROUP_NAME = "Rpc-From-Group";
    public static final String RPC_FROM_VERSION_NAME = "Rpc-From-Version";

    public static final String DEFAULT_RPC_FROM = "default";
    public static final String DEFAULT_RPC_FROM_API = "default";
    public static final String DEFAULT_RPC_FROM_GROUP = "default";
    public static final String DEFAULT_RPC_FROM_VERSION = "-1";
    private static final String GROUP_KEY = "APP_GROUP";
    private static final String VERSION_KEY = "APP_VERSION";

    private static final String[] NAMES = {
            "http_x_forwarded_for",
            "x-forwarded-for",
            "X-Original-Forwarded-For",
            "Proxy-Client-IP",
            "WL-Proxy-Client-IP",
            "HTTP_CLIENT_IP",
            "X-Real-IP"
    };

    private static String appId;
    private static String group;
    private static String version;
    private static final String LOCAL_IP = getLocalIp();
    private static final int LOCAL_PORT = 8080;
    private static final String LOCAL_ADDRESS = LOCAL_IP + ":" + LOCAL_PORT;

    static {
        appId = System.getProperty("PAAS_APP_NAME", System.getenv("PAAS_APP_NAME"));
        if (appId == null) {
            appId = "default";
        }
        group = System.getProperty(GROUP_KEY, System.getenv(GROUP_KEY));
        if (group == null) {
            group = "default";
        }
        version = System.getProperty(VERSION_KEY, System.getenv(VERSION_KEY));
        if (version == null) {
            version = "-1";
        }
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        DEAL_TIME_THREAD_LOCAL.set(System.currentTimeMillis());
        if(req instanceof HttpServletRequest) {
            req =  new ContentCachingRequestWrapper((HttpServletRequest) req);
        }
        // 执行请求
        try {
            chain.doFilter(req, res);
        } finally {
            postFilter(req, res);
        }
    }

    @Override
    public void destroy() {

    }

    private void postFilter(ServletRequest req, ServletResponse res) {
        try {
            Long start = DEAL_TIME_THREAD_LOCAL.get();
            if (start == null) {
                return;
            }
            long end = System.currentTimeMillis();
            // 访问日志
            doAccessLog(req, res, start, end);
        } finally {
            DEAL_TIME_THREAD_LOCAL.remove();
        }
    }

    /**
     * 打印访问日志
     *
     * @param req   请求
     * @param res   响应
     * @param start 请求起始时间
     * @param end   请求结束时间
     */
    private void doAccessLog(ServletRequest req, ServletResponse res, long start, long end) {
        try {
            long rt = end - start;
            HttpServletRequest request = (HttpServletRequest) req;
            HttpServletResponse response = (HttpServletResponse) res;
            // resolve url for pattern
            String url = request.getRequestURI();
            if (skipUrl(url, response)) {
                return;
            }
            Access access = new Access();
            access.setUrl(url);
            access.setAppId(appId.toLowerCase());
            access.setGroup(group);
            access.setVersion(version);
            access.setTimestamp(start);
            access.setTime(formatTime(start));
            access.setProtocol(request.getProtocol());
            access.setMethod(request.getScheme());
            access.setRequestMethod(request.getMethod());
            access.setHost(req.getServerName());
            access.setPort(request.getServerPort());
            access.setQuery(formatParams(request));
            access.setServerAddr(LOCAL_ADDRESS);
            access.setTraceId(request.getHeader("DC-TRACE-ID"));
            String skyId = TraceContext.traceId();
            if (!"".equals(skyId)) {
                access.setSkyId(skyId);
            }
            access.setApmId(org.slf4j.MDC.get("apm-traceid"));
            access.setRequestTime(rt);
            access.setUserId(request.getHeader("userId"));
            access.setStatus(response.getStatus());
            access.setClientIp(request.getRemoteHost());
            access.setRealClientIp(getClientIp(request));
            access.setRpcFrom(getRpcFromAppId(request).toLowerCase());
            access.setFromApi(getRpcFromApi(request));
            access.setFromGroup(getRpcFromGroup(request));
            access.setFromVersion(getRpcFromVersion(request));
            access.setReferer(request.getHeader("referer"));
            access.setUserAgent(request.getHeader("user-agent"));
            // 打印header
            if (isLogHeaders(access.getStatus())) {
                access.setHeaders(formatHeaders(request));
            }
            //打印request
            if (isLogRequest(access.getStatus())) {
                access.setRequest(getMessagePayload(request));
            }
            // 打印至access
            AccessLogger.info(access);
        } catch (Exception e) {
            logger.warn("webAccessFilter doAccessLog failed", e);
        }
    }

    /**
     * 是否跳过此请求
     *
     * @param url      url
     * @param response 响应
     * @return ..
     */
    private boolean skipUrl(String url, HttpServletResponse response) {
        if (url.endsWith("/actuator/health")) {
            return true;
        }
        String contentType = response.getContentType();
        return contentType.startsWith("application/javascript")
                || contentType.startsWith("text/css")
                || contentType.startsWith("image/")
                || contentType.startsWith("video/")
                || contentType.startsWith("text/html");
    }

    /**
     * 是否打印header
     *
     * @param httpCode 结果
     * @return ..
     */
    private boolean isLogHeaders(int httpCode) {
        return httpCode >= 300;
    }

    /**
     * 是否打印请求体
     *
     * @return boolean
     */
    private boolean isLogRequest(int httpCode) {
        // 失败的请求
        return httpCode >= 300;
    }

    private static String formatTime(long time) {
        Date date = new Date(time);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        return sdf.format(date);
    }

    private static String getRpcFromAppId(HttpServletRequest request) {
        String rpcFrom = request.getHeader(RPC_FROM_NAME);
        if (rpcFrom == null) {
            rpcFrom = request.getParameter(RPC_FROM_NAME);
        }
        return rpcFrom != null ? rpcFrom : DEFAULT_RPC_FROM;
    }

    private static String getRpcFromApi(HttpServletRequest request) {
        String rpcFrom = request.getHeader(RPC_FROM_API_NAME);
        return rpcFrom != null ? rpcFrom : DEFAULT_RPC_FROM_API;
    }

    private static String getRpcFromGroup(HttpServletRequest request) {
        String rpcFrom = request.getHeader(RPC_FROM_GROUP_NAME);
        return rpcFrom != null ? rpcFrom : DEFAULT_RPC_FROM_GROUP;
    }

    private static String getRpcFromVersion(HttpServletRequest request) {
        String rpcFrom = request.getHeader(RPC_FROM_VERSION_NAME);
        return rpcFrom != null ? rpcFrom : DEFAULT_RPC_FROM_VERSION;
    }

    /**
     * 获取请求体
     *
     * @param request 请求
     * @return {@link String}
     */
    protected String getMessagePayload(HttpServletRequest request) {
        if (!(request instanceof  ContentCachingRequestWrapper)) {
            return null;
        }
        ContentCachingRequestWrapper wrapper = (ContentCachingRequestWrapper) request;
        byte[] buf = wrapper.getContentAsByteArray();
        if (buf.length > 0) {
            try {
                return new String(buf, wrapper.getCharacterEncoding());
            } catch (UnsupportedEncodingException ex) {
                return "[unknown]";
            }
        }
        return null;
    }

    private static String formatParams(HttpServletRequest request) {
        Map<String, String[]> params = request.getParameterMap();
        if (params == null || params.isEmpty()) {
            return null;
        }
        return JSON.toJSONString(params);
    }

    private static String formatHeaders(HttpServletRequest request) {
        Map<String, List<String>> map = new HashMap<>();
        for (String headerName : Collections.list(request.getHeaderNames())) {
            map.put(headerName, Collections.list(request.getHeaders(headerName)));
        }
        return map.toString();
    }

    /**
     * 获取本机内网ip
     * @return ip
     */
    public static String getLocalIp() {
        // 获得本机的所有网络接口
        try {
            Enumeration<NetworkInterface> nifs = NetworkInterface.getNetworkInterfaces();
            while (nifs.hasMoreElements()) {
                NetworkInterface nif = nifs.nextElement();
                if (nif.isPointToPoint() || nif.isLoopback()) {
                    continue;
                }
                if (nif.getName().startsWith("docker")) {
                    continue;
                }
                // 获得与该网络接口绑定的 IP 地址，一般只有一个
                Enumeration<InetAddress> addresses = nif.getInetAddresses();

                while (addresses.hasMoreElements()) {
                    InetAddress addr = addresses.nextElement();
                    if (addr instanceof Inet4Address) {
                        return addr.getHostAddress();
                    }
                }
            }

            return "127.0.0.1";
        } catch (SocketException e) {
            throw new IllegalStateException(e);
        }
    }

    /**
     * 获取客户端ip
     * @param request 请求
     * @return 客户端ip
     */
    public static String getClientIp(HttpServletRequest request) {
        for (String name : NAMES) {
            String ip = resolveIp(request.getHeader(name));
            if (ip != null && !ip.isEmpty()) {
                return ip;
            }
        }
        return request.getRemoteHost();
    }

    private static String resolveIp(String ips) {
        String ip = null;
        if (ips == null || ips.isEmpty()) {
            return null;
        }
        if (ips.contains(",")) {
            ip = ips.split(",")[0].trim();
        }
        return ip;
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // do nothing
    }


}
